﻿--[[--------------------------------------------------------------------
	GridCodeBar.lua
----------------------------------------------------------------------]]

local GRIDCODE, GridCode = ...
local L = GridCode.L

local GridCodeStatus

local GridCodeRoster = GridCode:GetModule("GridCodeRoster")
local media = LibStub("LibSharedMedia-3.0", true)

local GridCodeBar = GridCode:NewModule("GridCodeBar", "AceBucket-3.0", "AceTimer-3.0")

------------------------------------------------------------------------

local _G = _G

--[[----------------------------------------------------------------------
--
--	Supported functions: bit.tobit, bit.tohex, bit.bnot, bit.band, bit.bor, bit.bxor,
--	                     bit.lshift, bit.rshift, bit.arshift, bit.rol, bit.ror, bit.bswap
--	Consistent semantics across 16, 32 and 64 bit platforms.
--	Supports different lua_Number types: either IEEE 754 doubles, int32_t or int64_t.
--	Runs on Linux, *BSD, Mac OS X, Windows and probably anything else you can find.
--	Simple installation on all systems. No bulky configure scripts. Embedded-systems-friendly.
--	Internal self-test on startup to detect miscompiles. Includes a comprehensive test and benchmark suite.
--	Compatible with the built-in bitwise operations in LuaJIT 2.0.
--	It's as fast as you can get with the standard Lua/C API.
--
------------------------------------------------------------------------]]
local bit = bit
if bit == nil then
	bit = require("bit")
end
local tobit, tohex = bit.tobit, bit.tohex
local bnot, band, bor, bxor = bit.bnot, bit.band, bit.bor, bit.bxor
local lshift, rshift, brol, bror = bit.lshift, bit.rshift, bit.rol, bit.ror
local bswap, arshift = bit.bswap, bit.arshift

------------------------------------------------------------------------

local function GridCodeBar_OnShow(self)
	GridCodeBar:SendMessage("GridCodeBar_UpdateFrameUnits")
	GridCodeBar:SendMessage("GridCode_UpdateLayoutSize")
end

local function GridCodeBar_OnAttributeChanged(self, name, value)
	if name == "unit" then
		GridCodeBar:SendMessage("GridCodeBar_UpdateFrameUnits")
	elseif name == "type1" and (not value or value == "") and self:CanChangeAttribute() then
		self:SetAttribute("type1", "target")
	end
end

GridCodeBar.InitialConfigFunction = function(self)
	GridCodeBar:RegisterFrame(self)

	self:SetScript("OnShow", GridCodeBar_OnShow)
	self:SetScript("OnAttributeChanged", GridCodeBar_OnAttributeChanged)
end

------------------------------------------------------------------------

local function HexDumpString(str, spacer)
	return (
		string.gsub(str, "(.)",
			function (c)
				return string.format("%02X%s", string.byte(c), spacer or "")
			end
		)
	)
end

-- Function decToHex (renamed, updated): http://lua-users.org/lists/lua-l/2004-09/msg00054.html
local function decToHex(IN)
	local B,K,OUT,I,D = 16,"0123456789ABCDEF","",0
	while IN > 0 do
		I = I + 1
		IN, D = math.floor(IN/B), math.fmod(IN,B)+1
		OUT = string.sub(K,D,D)..OUT
	end
	return "0x"..OUT
end

-- Function rgbToHex: http://gameon365.net/index.php
local function rgbToHex(c)
	local output = decToHex(c["r"]) .. decToHex(c["g"]) .. decToHex(c["b"])
	return output
end

local function HexToRGB(hex)
	local rhex, ghex, bhex
	if strlen(hex) == 6 then
		rhex, ghex, bhex = strmatch('([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})', hex)
	elseif strlen(hex) == 3 then
		rhex, ghex, bhex = strmatch('([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])', hex)
		if rhex and ghex and bhex then
			rhex = rhex .. rhex
			ghex = ghex .. ghex
			bhex = bhex .. bhex
		end
	end
	if not (rhex and ghex and bhex) then
		return 0, 0, 0
	else
		return tonumber(rhex, 16), tonumber(ghex, 16), tonumber(bhex, 16)
	end
end

local function GetRandomColor()
	local r, g, b
	r = band(math.floor(math.random() * 256), 0x000000ff)
	g = band(math.floor(math.random() * 256), 0x000000ff)
	b = band(math.floor(math.random() * 256), 0x000000ff)
	return (r * 65536 + g * 256 + b)
end

local function GetRandomColorA(alpha)
	local r, g, b
	r = band(math.floor(math.random() * 256), 0x000000ff)
	g = band(math.floor(math.random() * 256), 0x000000ff)
	b = band(math.floor(math.random() * 256), 0x000000ff)
	return (alpha * 16777216 + r * 65536 + g * 256 + b)
end

local function Color2RGB(valColor)
	if (not valColor) or (valColor == "") then
		return 0, 0, 0
	end
	local r, g, b
	valColor = math.floor(tonumber(valColor))
	b = band(valColor, 0x000000ff)
	g = band(rshift(valColor,  8), 0x000000ff)
	r = band(rshift(valColor, 16), 0x000000ff)
	return r/256, g/256, b/256
end

local function Color2RGBA(valColor)
	if (not valColor) or (valColor == "") then
		return 0, 0, 0, 1.0
	end
	local r, g, b, a
	valColor = math.floor(tonumber(valColor))
	b = band(valColor, 0x000000ff)
	g = band(rshift(valColor,  8), 0x000000ff)
	r = band(rshift(valColor, 16), 0x000000ff)
	a = band(rshift(valColor, 24), 0x000000ff)
	return r, g, b, a
end

------------------------------------------------------------------------

GridCodeBar.prototype = { }

function GridCodeBar:InitializeFrame(frame)
	--[[
	-- set media based on shared media
	local font = media and media:Fetch("font", self.db.profile.font) or STANDARD_TEXT_FONT
	local texture = media and media:Fetch("codebarbg", self.db.profile.texture) or "Interface\\Addons\\GridCode\\Textures\\black_128x128"

	for k, v in pairs(self.prototype) do
		frame[k] = v
	end

	-- set our left-click action
	if frame:CanChangeAttribute() then
		frame:SetAttribute("*type1", "target")
	end

	-- create border
	frame:SetBackdrop({
		bgFile = "Interface\\Addons\\GridCode\\Textures\\white_16x16", tile = true, tileSize = 16,
		edgeFile = "Interface\\Addons\\GridCode\\Textures\\white_16x16", edgeSize = 1,
		insets = {left = 1, right = 1, top = 1, bottom = 1},
	})
	frame:SetBackdropBorderColor(0,0,0,0)
	frame:SetBackdropColor(0,0,0,1)

	-- create bar BG (which users will think is the real bar, as it is the one that has a shiny color)
	-- this is necessary as there's no other way to implement status bars that grow in the other direction than normal
	frame.BarBG = frame:CreateTexture()
	frame.BarBG:SetTexture(texture)
	frame.BarBG:SetVertexColor(0, 0, 0, 1)
	frame.BarBG:SetPoint("CENTER", frame, "CENTER")

	-- set texture
	frame:SetNormalTexture(1,1,1,0)
	frame:EnableMouseoverHighlight(GridCodeBar.db.profile.enableMouseoverHighlight)

	if frame:CanChangeAttribute() then
		frame:SetAttribute("initial-width", self.db.profile.scale.codeBarWidth )
		frame:SetAttribute("initial-height", self.db.profile.scale.codeBarHeight)
	end

	--frame:Reset()
	--]]

	return frame
end

function GridCodeBar.prototype:Reset()
	--self:Hide()

	--[[
	self:SetAttribute("showPlayer", true)

	self:SetAttribute("showSolo", true)
	self:SetAttribute("showParty", true)
	self:SetAttribute("showRaid", true)

	self:SetAttribute("columnSpacing", nil)
	self:SetAttributeByProxy("columnAnchorPoint", nil)
	self:SetAttribute("groupBy", nil)
	self:SetAttribute("groupFilter", nil)
	self:SetAttribute("groupingOrder", nil)
	self:SetAttribute("maxColumns", nil)
	self:SetAttribute("nameList", nil)
	self:SetAttributeByProxy("point", nil)
	self:SetAttribute("sortDir", nil)
	self:SetAttribute("sortMethod", "NAME")
	self:SetAttribute("startingIndex", nil)
	self:SetAttribute("strictFiltering", nil)
	self:SetAttributeByProxy("unitsPerColumn", nil)
	self:SetAttribute("xOffset", nil)
	self:SetAttribute("yOffset", nil)
	--]]

	--self:UnregisterEvent("UNIT_NAME_UPDATE")
end

function GridCodeBar.prototype:SetAttributeByProxy(name, value)
	if name == "point" or name == "columnAnchorPoint" or name == "unitsPerColumn" then
		GridCodeBar:Debug("SetAttributeByProxy " .. name .. " " .. tostring(value) .. " " .. self:GetName())
		local count = 1
		local uframe = self:GetAttribute("child" .. count)
		while uframe do
			-- GridCodeBar:Debug("ClearAllPoints " .. uframe:GetName())
			uframe:ClearAllPoints()
			count = count + 1
			uframe = self:GetAttribute("child" .. count)
		end
	end
	self:SetAttribute(name, value)
end

-- shows the default unit tooltip
function GridCodeBar.prototype:OnEnter()
	--
end

function GridCodeBar.prototype:OnLeave()
	--
end

function GridCodeBar.prototype:SetCodeBarWidth(width)
	if not InCombatLockdown() then
		--self:SetWidth(width)
	end

	--local newWidth = width - (GridCodeBar.db.profile.borderSize + 1) * 2
	--self.Bar:SetWidth(newWidth)
	--self.BarBG:SetWidth(newWidth)
end

function GridCodeBar.prototype:SetCodeBarHeight(height)
	if not InCombatLockdown() then
		--self:SetHeight(height)
	end

	--local newHeight = height - (GridCodeBar.db.profile.borderSize + 1) * 2
	--self.Bar:SetHeight(newHeight)
	--self.BarBG:SetHeight(newHeight)
end

-- nil or false for vertical
function GridCodeBar.prototype:SetOrientation(horizontal)
	--self.orientation = orientation
	--self:PlaceCodeCells()
end

function GridCodeBar.prototype:SetCodeBarSpacing(codeBarSpacing)

	--self:SetCodeBarWidth(GridCodeBar.db.profile.scale.codeBarWidth)
	--self:SetCodeBarHeight(GridCodeBar.db.profile.scale.codeBarHeight)

	--self:PositionAllCodeCells()
end

function GridCodeBar.prototype:EnableMouseoverHighlight(enabled)
	--
end

-- return the number of visible units belonging to the GroupHeader
function GridCodeBar.prototype:GetVisibleUnitCount()
	local count = 0
	while self:GetAttribute("child" .. count) do
		count = count + 1
	end
	return count
end

function GridCodeBar.prototype:SetDataBuffer(byteBuffer, bufSize)
	--
end

------------------------------------------------------------------------

local NUM_CODEBARS = 0

function GridCodeBar_InitialConfigFunction(frame)
	--GridCode:GetModule("GridCodeBar").InitialConfigFunction(frame)
end

local y_Resolution_Default = 1050
local y_Resolution_Fixed = 768

function GridCodeBar_GetYResolution()
	local gxResolutionVal = GetCVar("gxResolution")
	local y_Resolution = y_Resolution_Default
	GridCode:Debug("gxResolutionVal()", gxResolutionVal)
	if gxResolutionVal and (gxResolutionVal ~= "") then
		local start = string.find(gxResolutionVal, "x")
		if start then
			y_ResolutionVal = string.sub(gxResolutionVal, start + 1)
			if y_ResolutionVal and (y_ResolutionVal ~= "") then
				y_Resolution = y_ResolutionVal
			end
		end
	end
	GridCode:Debug("y_Resolution", y_Resolution)
	return y_Resolution
end

function GridCodeBar_GetScaleMultiplier()
	local bUseScaleUI = GetCVar("useUiScale")
	local uiScaleCoeff = 1.0
	local y_Resolution = GridCodeBar_GetYResolution()
	local scaleMultiplier

	local uiScaleCoeffCVar = GetCVar("uiScale")
	if uiScaleCoeffCVar and (uiScaleCoeffCVar ~= "") then
		uiScaleCoeff = uiScaleCoeffCVar
	end

	if bUseScaleUI and (bUseScaleUI == "1" or bUseScaleUI == 1) then
		-- useUiScale = 1
		GridCode:Debug("bUseScaleUI = enabled")
		scaleMultiplier = y_Resolution_Fixed / y_Resolution
		scaleMultiplier = scaleMultiplier / uiScaleCoeff
	else
		-- useUiScale = 0
		GridCode:Debug("bUseScaleUI = disabled")
		--[[
		--scaleMultiplier = y_Resolution / y_Resolution_Fixed
		scaleMultiplier = y_Resolution_Fixed / y_Resolution
		if scaleMultiplier < 0.9 then
			scaleMultiplier = 0.9
		end
		scaleMultiplier = 1.0 / scaleMultiplier
		--]]
		scaleMultiplier = 1.0
	end
	GridCode:Debug("scaleMultiplier", scaleMultiplier)
	GridCode:Debug("uiScaleCoeff", uiScaleCoeff)
	GridCode:Debug("bUseScaleUI", bUseScaleUI)
	return scaleMultiplier, uiScaleCoeff, bUseScaleUI
end

--local frFixedScaleCoeff = 1.0 / 1.40625
local frFixedScaleCoeff = nil

function GridCodeBar_ActualToScreen(actPixel, uiScale)
	if not frFixedScaleCoeff then
		frFixedScaleCoeff, _, _ = GridCodeBar_GetScaleMultiplier()
	end
	--return ((actPixel * frFixedScaleCoeff) / uiScale) + 0.005
	return (actPixel * frFixedScaleCoeff) + 0.0005
end

------------------------------------------------------------------------

GridCodeBar.defaultDB = {
	debug = false,

	Padding = 1,
	Spacing = 10,

	ScaleSize = 1.0,

	signWidth = 64,
	signHeight = 8,
	signAlpha = 1.0,
	signPaddingY = 4,

	maxColums = 20,
	maxRows = 20,
	cellWidth = 2,
	cellHeight = 2,
	intervalX = 4,
	intervalY = 4,
	intervalPadding = 4,
	nChecksumLine = 1,
	nChecksumNone = 1,
	nChecksumTail = 2,

	areaAlpha = 1.0,

	codeBarSpacing = 4,
	codeBarAlpha = 1.0,
}

GridCodeBar.bInitedCells = false;

GridCodeBar.scale = {
	bInited = false,
	bVisibled = true,

	signWidth = 64,
	signHeight = 8,
	signPaddingY = 4,

	cellWidth = 4,
	cellHeight = 4,
	intervalPadding = 4,

	areaWidth = 0,
	areaHeight = 0,

	codeBarSpacing = 4,
	codeBarWidth = 0,
	codeBarHeight = 0,
}

GridCodeBar.codeAreas = { }

GridCodeBar.cache = {
	RGBStreams = { },
}

------------------------------------------------------------------------

local ORDER_LAYOUT = 20
local ORDER_DISPLAY = 30

GridCodeBar.options = {
	name = L["CodeBar"],
	desc = L["Options for GridCodeBar."],
	disabled = InCombatLockdown,
	type = "group",
	args = {
		["padding"] = {
			name = L["Padding"],
			desc = L["Adjust frame padding."],
			order = 120,
			width = "double",
			type = "range", max = 20, min = 0, step = 1,
			get = function()
				return GridCodeBar.db.profile.Padding
			end,
			set = function(_, v)
				GridCodeBar.db.profile.Padding = v
				GridCodeBar:ReloadLayout()
			end,
		},
		["spacing"] = {
			name = L["Spacing"],
			desc = L["Adjust frame spacing."],
			order = 130,
			width = "double",
			type = "range", min = 0, max = 25, step = 1,
			get = function()
				return GridCodeBar.db.profile.Spacing
			end,
			set = function(_, v)
				GridCodeBar.db.profile.Spacing = v
				GridCodeBar:ReloadLayout()
			end,
		},
		["scale"] = {
			name = L["Scale"],
			desc = L["Adjust GridCode scale."],
			order = 140,
			width = "double",
			type = "range", min = 0.5, max = 2.0, step = 0.05, isPercent = true,
			get = function()
				return GridCodeBar.db.profile.ScaleSize
			end,
			set = function(_, v)
				GridCodeBar.db.profile.ScaleSize = v
				GridCodeBar:Scale()
			end,
		},
	},
}

if media then
	local mediaWidgets = media and LibStub("AceGUISharedMediaWidgets-1.0", true)
end

------------------------------------------------------------------------

GridCodeBar.debugFrame = ChatFrame1
GridCodeBar.debugging = false

function GridCodeBar:Debug(str, ...)
	if not self.debugging then return end
	if not str or str:len() == 0 then return end
	if (...) then
		if str:find("%%[dfs%d%.]") then
			str = str:format(...)
		else
			str = strjoin(" ", str, tostringall(...))
		end
	end
	self.debugFrame:AddMessage("|cffff9933GridCodeBar:|r " .. str)
end

function GridCodeBar:PostInitialize()
	GridCodeStatus = GridCode:GetModule("GridCodeStatus")

	self.debugging = self.db.profile.debug

	self.frames = {}
	self.registeredFrames = {}

	self.barFrame = nil
	self.frame = nil
end

function GridCodeBar:OnEnable()
	self:Debug("OnEnable")

	--[[
	self:Scale()

	self:RegisterMessage("GridCodeBar_StatusGained")
	self:RegisterMessage("GridCodeBar_StatusLost")

	self:RegisterMessage("GridCode_StatusRegistered", "UpdateOptionsMenu")
	self:RegisterMessage("GridCode_StatusUnregistered", "UpdateOptionsMenu")

	self:RegisterMessage("GridCode_ColorsChanged", "UpdateAllFrames")

	self:RegisterEvent("PLAYER_ENTERING_WORLD", "UpdateFrameUnits")

	self:RegisterEvent("UNIT_ENTERED_VEHICLE", "SendMessage_UpdateFrameUnits")
	self:RegisterEvent("UNIT_EXITED_VEHICLE", "SendMessage_UpdateFrameUnits")
	self:RegisterMessage("GridCode_RosterUpdated", "SendMessage_UpdateFrameUnits")

	self:RegisterMessage("GridCodeBar_UpdateFrameUnits", "UpdateFrameUnits")

	self:RegisterMessage("GridCode_EnteringCombat", "EnteringOrLeavingCombat")
	self:RegisterMessage("GridCode_LeavingCombat", "EnteringOrLeavingCombat")
	--]]

	self:Reset()
end

function GridCodeBar:PostEnable()
	self:Debug("PostEnable")
end

function GridCodeBar:PostDisable()
	if self.frame.codeBar then
		self.frame.codeBar:Hide()
	end
end

function GridCodeBar:SendMessage_UpdateFrameUnits()
	self:SendMessage("GridCodeBar_UpdateFrameUnits")
end

function GridCodeBar:PostReset()
	self:ReloadLayout()
	-- position and scale frame
	self:RestorePosition()
	self:Scale()

	self:ResetAllFrames()
	self:UpdateFrameUnits()
	self:UpdateAllFrames()
end

------------------------------------------------------------------------

function GridCodeBar:RegisterFrame(frame)
	self:Debug("RegisterFrame", frame:GetName())

	self.registeredFrameCount = (self.registeredFrameCount or 0) + 1
	self.registeredFrames[frame:GetName()] = self:InitializeFrame(frame)
	self:UpdateFrameUnits()
end

function GridCodeBar:WithAllFrames(func)
	for _, frame in pairs(self.registeredFrames) do
		func(frame)
	end
end

function GridCodeBar:ResetAllFrames()
	self:WithAllFrames(function(f)
		f:Reset()
	end)
	self:SendMessage("GridCode_UpdateLayoutSize")
end

function GridCodeBar:ResizeAllFrames()
	self:WithAllFrames(function(f)
		f:SetCodeBarWidth(self.db.profile.scale.codeBarWidth)
		f:SetCodeBarHeight(self.db.profile.scale.codeBarHeight)
	end)
	self:SendMessage("GridCode_UpdateLayoutSize")
end

function GridCodeBar:UpdateAllFrames()
	self:WithAllFrames(function(f)
		GridCodeBar:UpdateCodeCells(f)
	end)
end

------------------------------------------------------------------------

function GridCodeBar:UpdateFrameUnits()
	for frame_name, frame in pairs(self.registeredFrames) do
		if frame:IsVisible() then
			local old_unit = frame.unit
			local old_guid = frame.unitGUID
			local unitid = SecureButton_GetModifiedUnit(frame)
				  unitid = unitid and unitid:gsub("petpet", "pet") -- http://forums.wowace.com/showpost.php?p=307619&postcount=3174
			local guid = unitid and UnitGUID(unitid) or nil

			if old_unit ~= unitid or old_guid ~= guid then
				self:Debug("Updating", frame_name, "to", unitid, guid, "was", old_unit, old_guid)

				if unitid then
					frame.unit = unitid
					frame.unitGUID = guid

					if guid then
						self:UpdateCodeCells(frame)
					end
				else
					frame.unit = nil
					frame.unitGUID = nil

					self:ClearCodeCells(frame)
				end
			end
		end
	end
end

function GridCodeBar:UpdateCodeCells(frame)
	local unitid = frame.unit
	if not unitid then return end

	-- self.statusmap[indicator][status]
	for indicator in pairs(self.db.profile.statusmap) do
		self:UpdateCodeCell(frame, indicator)
	end
end

function GridCodeBar:ClearCodeCells(frame)
	for indicator in pairs(self.db.profile.statusmap) do
		frame:ClearCodeCell(indicator)
	end
end

function GridCodeBar:UpdateCodeCellsForStatus(frame, status)
	--[[
	local unitid = frame.unit
	if not unitid then return end

	-- self.statusmap[indicator][status]
	local statusmap = self.db.profile.statusmap
	for indicator, map_for_indicator in pairs(statusmap) do
		if map_for_indicator[status] then
			self:UpdateCodeCell(frame, indicator)
		end
	end
	--]]
end

function GridCodeBar:UpdateCodeCell(frame, indicator)
	local status = self:StatusForCodeCell(frame.unit, frame.unitGUID, indicator)
	if status then
		-- self:Debug("Showing status", status.text, "for", name, "on", indicator)
		frame:SetCodeCell(indicator,
			status.color,
			status.text,
			status.value,
			status.maxValue,
			status.texture,
			status.start,
			status.duration,
			status.stack,
			status.texCoords)
	else
		-- self:Debug("Clearing indicator", indicator, "for", name)
		frame:ClearCodeCell(indicator)
	end
end

function GridCodeBar:StatusForCodeCell(unitid, guid, indicator)
	local topPriority = 0
	local topStatus
	--[[
	local statusmap = self.db.profile.statusmap[indicator]

	-- self.statusmap[indicator][status]
	for statusName, enabled in pairs(statusmap) do
		local status = enabled and GridCodeStatus:GetCachedStatus(guid, statusName)
		if status then
			local valid = true

			-- make sure the status can be displayed
			if (indicator == "text" or indicator == "text2") and not status.text then
				self:Debug("unable to display", statusName, "on", indicator, ": no text")
				valid = false
			end
			if indicator == "icon" and not status.texture then
				self:Debug("unable to display", statusName, "on", indicator, ": no texture")
				valid = false
			end

			if status.range and type(status.range) ~= "number" then
				self:Debug("range not number for", statusName)
				valid = false
			end

			if status.priority and type(status.priority) ~= "number" then
				self:Debug("priority not number for", statusName)
				valid = false
			end

			-- only check range for valid statuses
			if valid then
				local inRange = not status.range or self:UnitInRange(unitid, status.range)

				if ((status.priority or 99) > topPriority) and inRange then
					topStatus = status
					topPriority = topStatus.priority
				end
			end
		end
	end
	--]]

	return topStatus
end

------------------------------------------------------------------------

local GridCodeStatusRange
function GridCodeBar:UnitInRange(id, yrds)
	if not id or not UnitExists(id) then return false end

	if not GridCodeStatusRange then
		GridCodeStatusRange = GridCode:GetModule("GridCodeStatus"):GetModule("GridCodeStatusRange")
	end

	return GridCodeStatusRange and GridCodeStatusRange:UnitInRange(id) or UnitInRange(id)
end

------------------------------------------------------------------------

function GridCodeBar:GridCodeBar_StatusGained(event, guid, status, priority, range, color, text, value, maxValue, texture, start, duration, stack)
	for _, frame in pairs(self.registeredFrames) do
		if frame.unitGUID == guid then
			self:UpdateCodeCellsForStatus(frame, status)
		end
	end
end

function GridCodeBar:GridCodeBar_StatusLost(event, guid, status)
	for _, frame in pairs(self.registeredFrames) do
		if frame.unitGUID == guid then
			self:UpdateCodeCellsForStatus(frame, status)
		end
	end
end

function GridCodeBar:UpdateOptionsMenu()
	self:Debug("UpdateOptionsMenu()")
end

------------------------------------------------------------------------

local reloadLayoutQueued
local updateSizeQueued
function GridCodeBar:EnteringOrLeavingCombat()
	self:Debug("GridCodeBar:EnteringOrLeavingCombat()")
	if reloadLayoutQueued then return self:PartyTypeChanged() end
	if updateSizeQueued then return self:PartyMembersChanged() end
end

function GridCodeBar:CombatFix()
	self:Debug("CombatFix")
end

function GridCodeBar:PartyMembersChanged()
	self:Debug("PartyMembersChanged")

	if InCombatLockdown() then
		updateSizeQueued = true
	else
		self:UpdateSize()
		updateSizeQueued = false
	end
end

function GridCodeBar:PartyTypeChanged()
	self:Debug("PartyTypeChanged")

	if InCombatLockdown() then
		reloadLayoutQueued = true
	else
		reloadLayoutQueued = false
		updateSizeQueued = false
	end
end

------------------------------------------------------------------------

local function print_table(t)
	if type(t) ~= "nil" then
		for i, v in pairs(t) do
			if type(v) == "table" then
				print_table(v)
			elseif type(v) == "nil" then
				print(i.." = nil")
			else
				print(i.." = "..tostring(v))
			end
		end
	end
end

local function GridCodeBar_OnMouseDown(self, button)
	if not self then return end
	--GridCodeBar:Debug("GridCodeBar_OnMouseDown")
	GridCodeBar:GenerateCodeBarCellRandomColors()
end

local function GridCodeBar_OnMouseUp(self)
	if not self then return end
	--GridCodeBar:Debug("GridCodeBar_OnMouseUp")
	GridCodeBar:RenderCodeBarCells()
end

local function GridCodeBar_OnEnter(self)
	--
end

local function GridCodeBar_OnLeave(self)
	--GameTooltip:Hide()
end

------------------------------------------------------------------------

-----------------------------------------------------------
-- Calculation or recalculation CodeBar's scale detail
-----------------------------------------------------------
function GridCodeBar:CalcCodeBarScale(bForceUpdate)
	local settings = self.db.profile
	local codebars_scale = self.scale

	if self.scale.bInited and (not bForceUpdate) then return end

	local codebars = settings
	local padding = settings.Padding
	local spacing = settings.Spacing

	local scale = self.frame:GetEffectiveScale()
	local uiScale = UIParent:GetEffectiveScale()
	self:Debug("f:GetEffectiveScale()", scale, "UIParent:GetEffectiveScale()", uiScale)

	--self:Debug("self.db.profile.padding:", padding)
	--self:Debug("self.db.profile.spacing:", spacing)

	local signWidth, signHeight = codebars.signWidth, codebars.signHeight
	local signAlpha = codebars.signAlpha
	local signPaddingY = codebars.signPaddingY

	local maxColums, maxRows = codebars.maxColums, codebars.maxRows
	local cellWidth, cellHeight = codebars.cellWidth, codebars.cellHeight
	local intervalX, intervalY, intervalPadding = codebars.intervalX, codebars.intervalY, codebars.intervalPadding
	local nChecksumLine, nChecksumNone, nChecksumTail = codebars.nChecksumLine, codebars.nChecksumNone, codebars.nChecksumTail
	local actColums = maxColums + nChecksumLine + nChecksumNone

	local areaWidth = actColums * cellWidth + math.floor((actColums - 1) / intervalX + 0.01) * intervalPadding
	local areaHeight = maxRows * cellHeight + math.floor((maxRows - 1) / intervalY + 0.01) * intervalPadding
	local areaAlpha = codebars.areaAlpha

	local codeBarSpacing = codebars.codeBarSpacing
	local codeBarWidth = ((signWidth > areaWidth) and signWidth or areaWidth) + codeBarSpacing * 2
	local codeBarHeight = signHeight + signPaddingY + areaHeight + codeBarSpacing * 2
	local codeBarAlpha = codebars.codeBarAlpha

	self:Debug("signWidth:", signWidth)
	self:Debug("signHeight:", signHeight)
	self:Debug("areaWidth:", areaWidth)
	self:Debug("areaHeight:", areaHeight)
	self:Debug("codeBarWidth:", codeBarWidth)
	self:Debug("codeBarHeight:", codeBarHeight)
	self:Debug("codeBarSpacing:", codeBarSpacing)

	signWidth = GridCodeBar_ActualToScreen(signWidth, scale)
	signHeight = GridCodeBar_ActualToScreen(signHeight, scale)
	signPaddingY = GridCodeBar_ActualToScreen(signPaddingY, scale)

	cellWidth = GridCodeBar_ActualToScreen(cellWidth, scale)
	cellHeight = GridCodeBar_ActualToScreen(cellHeight, scale)
	intervalPadding = GridCodeBar_ActualToScreen(intervalPadding, scale)

	areaWidth = GridCodeBar_ActualToScreen(areaWidth, scale)
	areaHeight = GridCodeBar_ActualToScreen(areaHeight, scale)

	codeBarWidth = GridCodeBar_ActualToScreen(codeBarWidth, scale)
	codeBarHeight = GridCodeBar_ActualToScreen(codeBarHeight, scale)
	codeBarSpacing = GridCodeBar_ActualToScreen(codeBarSpacing, scale)

	self:Debug("codeBarSpacing:", codeBarSpacing)

	codebars_scale.signWidth = signWidth
	codebars_scale.signHeight = signHeight
	codebars_scale.signPaddingY = signPaddingY

	codebars_scale.cellWidth = cellWidth
	codebars_scale.cellHeight = cellHeight
	codebars_scale.intervalPadding = intervalPadding

	codebars_scale.areaWidth = areaWidth
	codebars_scale.areaHeight = areaHeight

	codebars_scale.codeBarWidth = codeBarWidth
	codebars_scale.codeBarHeight = codeBarHeight
	codebars_scale.codeBarSpacing = codeBarSpacing

	self.scale.bInited = true
end

function GridCodeBar:GetCodeBarSize(bForceUpdate)
	self:CalcCodeBarScale(bForceUpdate)

	local p = self.db.profile
	local codeBarWidth, codeBarHeight = self.scale.codeBarWidth, self.scale.codeBarHeight
	local Padding, Spacing = p.Padding, p.Spacing

	codeBarWidth = codeBarWidth + Spacing * 2
	codeBarHeight = codeBarHeight + Spacing * 1
	return codeBarWidth, codeBarHeight
end

function GridCodeBar:UpdateSpacing(spacing)
	local settings = self.db.profile
	settings.Spacing = spacing
end

function GridCodeBar:UpdatePadding(padding)
	local settings = self.db.profile
	settings.Padding = padding
end

----------------------------------------------------------------
-- GcProtoBuf Test
----------------------------------------------------------------
function GridCodeBar:ProtoBuf_test()
	local GcProtoBuf = LibStub("GcProtoBuf-1.0")
	local pbuf = GcProtoBuf:new()

	pbuf:push(PbType.bool, false, true, 2, 0, nil, 1)
	pbuf:push(PbType.int8, 11, 213)
	pbuf:push(PbType.int32, 1001, 3221, 12331)

	pbuf:push(PbType.uint8, 11, 300)
	pbuf:push(PbType.uint32, 67328, 32213, 731234)

	pbuf:push(PbType.string, "i'm a string", 888888, nil, true)

	pbuf:push(PbType.float, 0, 0.23213, 1332.483943)
	pbuf:push(PbType.double, 0, 0.23213, 1332.483943)

	pbuf:push(PbType.binary, "0011223344", 0, 1000)

	self:Debug("pbuf.core.length", pbuf.core.length)
	self:Debug("pbuf.core.data_size", pbuf.core.data_size)

	pbuf:list()
end

function GridCodeBar:CreateCodeBar(f, spacing, padding)
	self:Debug("CreateCodeBar()", spacing, padding)

	-----------------------------------------------------------------
	-- CodeBar Frame Define
	if not f then return end
	self.frame = f

	local settings = self.db.profile
	settings.Padding = padding
	settings.Spacing = spacing

	self:ProtoBuf_test()
	self:CalcCodeBarScale(false)

	local codebars = settings
	local codebars_scale = self.scale

	local signWidth, signHeight
	local signAlpha = codebars.signAlpha
	local signPaddingY

	local maxColums, maxRows = codebars.maxColums, codebars.maxRows
	local cellWidth, cellHeight
	local intervalX, intervalY, intervalPadding = codebars.intervalX, codebars.intervalY, 0
	local nChecksumLine, nChecksumNone, nChecksumTail = codebars.nChecksumLine, codebars.nChecksumNone, codebars.nChecksumTail
	local actColums = maxColums + nChecksumLine + nChecksumNone

	local areaWidth, areaHeight
	local areaAlpha = codebars.areaAlpha

	local codeBarSpacing = codebars.codeBarSpacing
	local codeBarWidth, codeBarHeight
	local codeBarAlpha = codebars.codeBarAlpha

	signWidth = codebars_scale.signWidth
	signHeight = codebars_scale.signHeight
	signPaddingY = codebars_scale.signPaddingY

	cellWidth = codebars_scale.cellWidth
	cellHeight = codebars_scale.cellHeight
	intervalPadding = codebars_scale.intervalPadding

	areaWidth = codebars_scale.areaWidth
	areaHeight = codebars_scale.areaHeight

	codeBarWidth = codebars_scale.codeBarWidth
	codeBarHeight = codebars_scale.codeBarHeight
	codeBarSpacing = codebars_scale.codeBarSpacing

	---------------------------------
	-- CodeBar Frame
	---------------------------------
	f.codeBar = CreateFrame("Frame", "GridCodeBarFrame", f)
	f.codeBar:SetWidth(codeBarWidth)
	f.codeBar:SetHeight(codeBarHeight)
	f.codeBar:EnableMouse(true)
	f.codeBar:RegisterForDrag("LeftButton")
	f.codeBar:SetScript("OnMouseDown", GridCodeBar_OnMouseDown)
	f.codeBar:SetScript("OnMouseUp", GridCodeBar_OnMouseUp)
	f.codeBar:SetScript("OnHide", GridCodeBar_OnMouseUp)
	f.codeBar:SetPoint("TOPLEFT", f, "TOPLEFT", spacing, -spacing)
	-- f.codeBar:SetFrameStrata("FULLSCREEN_DIALOG")
	f.codeBar:SetFrameStrata("TOOLTIP")
	f.codeBar:SetFrameLevel(127)
	f.codeBar:SetScale(1.0)
	--f.codeBar:Hide()

	-- create CodeBar bg texture
	f.codeBarBg = f.codeBar:CreateTexture(nil, "BORDER")
	f.codeBarBg:SetTexture("Interface\\AddOns\\GridCode\\Textures\\black_128x128")
	-- f.codeBarBg:SetTexture(0, 0, 0, 1.0)
	-- f.codeBarBg:SetTexCoord(0, 1, 0, 1)
	f.codeBarBg:SetWidth(codeBarWidth)
	f.codeBarBg:SetHeight(codeBarHeight)
	f.codeBarBg:SetAlpha(codeBarAlpha)
	f.codeBarBg:SetPoint("TOPLEFT", f.codeBar, "TOPLEFT", 0, 0)
	-- f.codeBarBg:SetVertexColor(0, 0, 0, 1.0)

	---------------------------------
	-- create CodeBar's top sign
	---------------------------------
	f.topSign = CreateFrame("Frame", "GridCodeBarFrameTopSign", f.codeBar)
	f.topSign:SetWidth(signWidth)
	f.topSign:SetHeight(signHeight)
	f.topSign:SetPoint("TOPLEFT", f.codeBar, "TOPLEFT", codeBarSpacing, -codeBarSpacing)
	f.topSign:SetScale(1.0)
	--f.topSign:Hide()

	-- create top sign's texture
	f.topSignTexture = f.topSign:CreateTexture("GridCodeBarFrameTopSignTexture", "ARTWORK")
	f.topSignTexture:SetTexture("Interface\\AddOns\\GridCode\\Textures\\locate_sign_256x32")
	--f.topSignTexture:SetTexCoord(0, 1, 0, 1)
	f.topSignTexture:SetWidth(signWidth)
	f.topSignTexture:SetHeight(signHeight)
	f.topSignTexture:SetAlpha(signAlpha)
	f.topSignTexture:SetPoint("TOPLEFT", f.topSign, "TOPLEFT", 0, 0)

	-------------------------------------
	-- create CodeBar's code area
	-------------------------------------
	f.codeArea = CreateFrame("Frame", "GridCodeBarFrameCodeArea", f.codeBar)
	f.codeArea:SetWidth(areaWidth)
	f.codeArea:SetHeight(areaHeight)
	f.codeArea:SetPoint("TOPLEFT", f.topSign, "BOTTOMLEFT", 0, -signPaddingY)
	f.codeArea:SetScale(1.0)
	--f.codeArea:Hide()

	self:Debug("CreateCodeBar(): create code area's cell texture", spacing, padding)

	-------------------------------------
	-- create code area's cell texture
	-------------------------------------
	local col, row
	local num_cells, num_checksums = 1, 1
	local codeBarCell
	local prevCell, prevLineCell = nil, nil
	local cellName, isCheckSumCell
	local valColor
	for row = 1, maxRows do
		for col = 1, actColums do	-- actColums = maxColums + nChecksumLine + nChecksumNone
			if col > maxColums then
				isCheckSumCell = true
				if col > (actColums - nChecksumNone) then
					cellName = "GridCodeBarFrame_CodeAreaCheckSumNone"..row
				else
					cellName = "GridCodeBarFrame_CodeAreaCheckSum"..num_checksums
					num_checksums = num_checksums + 1
				end
			else
				isCheckSumCell = false
				cellName = "GridCodeBarFrame_CodeAreaCell"..num_cells
				self.cache.RGBStreams[num_cells] = 0
				num_cells = num_cells + 1
			end
			if col > (actColums - nChecksumNone) then
				valColor = 0x00FFFFFF
			else
				valColor = GetRandomColor()
			end
			if col == 1 then
				codeBarCell = self:CreateCodeBarCell(f.codeArea, prevLineCell, cellName, valColor, col, row)
				if codeBarCell then
					prevLineCell = codeBarCell
				end
			else
				codeBarCell = self:CreateCodeBarCell(f.codeArea, prevCell, cellName, valColor, col, row)
			end
			if codeBarCell then
				prevCell = codeBarCell
				table.insert(self.codeAreas, codeBarCell)
			end
		end
	end

	self.barFrame = f.codeBar
	self.frame = f

	self.bInitedCells = true

	return f.codeBar
end

function GridCodeBar:CreateCodeBarCell(parent, prevCell, cellName, valColor, col, row)
	if not parent then return end
	--self:Debug("CreateCodeBarCell():", cellName)

	local codebars = self.db.profile
	local codebars_scale = self.scale
	local cellWidth, cellHeight = codebars_scale.cellWidth, codebars_scale.cellHeight
	local intervalX, intervalY, intervalPadding = codebars.intervalX, codebars.intervalY, codebars_scale.intervalPadding
	local nChecksumLine, nChecksumTail = codebars.nChecksumLine, codebars.nChecksumTail
	local xIntervalNeed, yIntervalNeed
	local r, g, b = Color2RGB(valColor)

	-- "GridCodeBarFrameCodeAreaXX" or "GridCodeBarFrameCodeAreaCheckSumXX"
	local codeBarCell = parent:CreateTexture(cellName, "ARTWORK")
	codeBarCell:SetTexture("Interface\\AddOns\\GridCode\\Textures\\white_16x16")
	--codeBarCell:SetTexCoord(0, 1, 0, 1)
	codeBarCell:SetWidth(cellWidth)
	codeBarCell:SetHeight(cellHeight)
	codeBarCell:SetAlpha(1.0)
	if (prevCell == nil) or (col == 1 and row == 1) then
		codeBarCell:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, 0)
	else
		if (col == 1) and (row ~= 1) then
			yIntervalNeed = (((row - 1) % intervalY) == 0) and true or false
			if yIntervalNeed then
				codeBarCell:SetPoint("TOPLEFT", prevCell, "BOTTOMLEFT", 0, -intervalPadding)
			else
				codeBarCell:SetPoint("TOPLEFT", prevCell, "BOTTOMLEFT", 0, 0)
			end
		else
			xIntervalNeed = (((col - 1) % intervalX) == 0) and true or false
			if xIntervalNeed then
				codeBarCell:SetPoint("TOPLEFT", prevCell, "TOPRIGHT", intervalPadding, 0)
			else
				codeBarCell:SetPoint("TOPLEFT", prevCell, "TOPRIGHT", 0, 0)
			end
		end
	end
	codeBarCell:SetVertexColor(r, g, b, 1.0)
	--codeBarCell:Reset()
	return codeBarCell
end

function GridCodeBar:GenerateCodeBarCellRandomColors()
	--GridCodeBar:Debug("GridCodeBar:GenerateCodeBarCellRandomColors()")
	if not self then return end
	if not self.cache.RGBStreams then return end

	local settings = self.db.profile
	local maxColums, maxRows = settings.maxColums, settings.maxRows

	local col, row
	local num_cells = 1

	for row = 1, maxRows do
		for col = 1, maxColums do
			self.cache.RGBStreams[num_cells] = GetRandomColor()
			num_cells = num_cells + 1
		end
	end
end

function GridCodeBar:RenderCodeBarCells()
	--GridCodeBar:Debug("GridCodeBar:RenderCodeBarCells()")
	if not self then return end
	if not self.bInitedCells then return end
	--if (not self.frame) or (not self.barFrame) then return end

	local settings = self.db.profile

	local maxColums, maxRows = settings.maxColums, settings.maxRows
	local nChecksumLine, nChecksumNone, nChecksumTail = settings.nChecksumLine, settings.nChecksumNone, settings.nChecksumTail
	local actColums = maxColums + nChecksumLine + nChecksumNone

	local col, row
	local num_cells, num_checksums = 1, 1
	local cellName, codeBarCell, isCheckSumCell
	local valColor, r, g, b
	local checksumLine, checksumLineAdd

	for row = 1, maxRows do
		checksumLine = 0
		checksumLineAdd = 0
		for col = 1, actColums - nChecksumNone do	-- actColums = maxColums + nChecksumLine + nChecksumNone
			if col > maxColums then
				cellName = "GridCodeBarFrame_CodeAreaCheckSum"..num_checksums
				num_checksums = num_checksums + 1
				isCheckSumCell = true
			else
				cellName = "GridCodeBarFrame_CodeAreaCell"..num_cells
				num_cells = num_cells + 1
				isCheckSumCell = false
			end
			codeBarCell = _G[cellName]
			if codeBarCell then
				if isCheckSumCell then
					valColor = bxor(band(checksumLine, 0x00ffffff), band(checksumLineAdd, 0x000000ff))
				else
					valColor = self.cache.RGBStreams[num_cells]
					if type(valColor) == "number" then
						checksumLine = bxor(checksumLine, valColor)
						checksumLineAdd = checksumLineAdd + valColor
					else
						valColor = 0
					end
				end
				r, g, b = Color2RGB(valColor)
				codeBarCell:SetVertexColor(r, g, b, 1.0)
			end
		end
	end
end

local previousGroup
function GridCodeBar:PlaceGroup(layoutGroup, groupNumber)
	--
end

function GridCodeBar:ReloadLayout()
	--self:SendMessage("GridCode_ReloadLayout")
end

function GridCodeBar:LoadLayout(layoutName)
	self.db.profile.layout = layoutName
	if InCombatLockdown() then
		reloadLayoutQueued = true
		return
	end

	self:UpdateDisplay()
end

function GridCodeBar:UpdateDisplay()
	self:UpdateColor()
	self:UpdateVisibility()
	self:UpdateSize()
end

function GridCodeBar:UpdateVisibility()
	if self.db.profile.bVisibled then
		self.frame.codeBar:Hide()
	else
		self.frame.codeBar:Show()
	end
end

function GridCodeBar:UpdateSize()
	--self:Debug("GridCodeBar:UpdateSize()")

	local codeBarWidth, codeBarHeight
	codeBarWidth, codeBarHeight = self:GetCodeBarSize()

	self.frame.codeBar:SetWidth(codeBarWidth)
	self.frame.codeBar:SetHeight(codeBarHeight)
end

function GridCodeBar:UpdateColor()
	local settings = self.db.profile
	--
end

function GridCodeBar:SavePosition()
	--
end

function GridCodeBar:ResetPosition()
	self:RestorePosition()
	self:SavePosition()
end

function GridCodeBar:RestorePosition()
	self:Debug("Restored Position")
end

function GridCodeBar:Scale()
	self:SavePosition()
	if self.frame then
		self.frame.codeBar:SetScale(self.db.profile.ScaleSize)
	end
	self:RestorePosition()
end

------------------------------------------------------------------------

local function findVisibleUnitFrame(f)
	if f:IsVisible() and f:GetAttribute("unit") then
		return f
	end

	for i = 1, select('#', f:GetChildren()) do
		local child = select(i, f:GetChildren())
		local good = findVisibleUnitFrame(child)

		if good then
			return good
		end
	end
end

function GridCodeBar:FakeSize(width, height)
	local p = self.db.profile

	local f = findVisibleUnitFrame(self.frame.codeBar)

	if not f then
		self:Debug("No suitable frame found.")
		return
	else
		self:Debug(("Using %s"):format(f:GetName()))
	end

	local codeBarWidth = f:GetWidth()
	local codeBarHeight = f:GetHeight()

	local x = codeBarWidth * width + (width - 1) * p.Padding + p.Spacing * 2
	local y = codeBarHeight * height + (height - 1) * p.Padding + p.Spacing * 2

	self.frame.codeBar:SetWidth(x)
	self.frame.codeBar:SetHeight(y)
end

SLASH_GRIDCODEBAR1 = "/gridcodebar"
SLASH_GRIDCODEBAR2 = "/gcbar"

SlashCmdList.GRIDCODEBAR = function(cmd)
	local width, height = cmd:trim():match("^(%d+) ?(%d*)$")
	width, height = tonumber(width), tonumber(height)

	if not width then return end
	if not height then height = width end

	GridCodeBar:Debug("/gridcodebar", width, height)

	GridCodeBar:FakeSize(width, height)
end
